module SRI
  def self.eval( e, prog, v = Scope.new )
    puts "Evaluating an #{e.class}" if DEBUG
    
    case e
    when Literal
      e.value
    when Constant
      prog.constants[ e.name ]
    when Variable
      v[ e.name.to_sym ]
    when Conditional
      eval( e.cond, prog, v ) ? eval( e.e1, prog, v ) : eval( e.e2, prog, v )
    when IVarAssignment
      puts "++ v[:self] = #{v[:self].inspect}"
      instance = v[ :self ]
      instance.ivars[ e.identifier ] = eval( e.expr, prog, Scope.new( v ) )
    when Assignment
      v[ e.identifier ] = eval( e.expr, prog, v )
    when Return
      eval( e.value, prog, v )
    when FunctionCall
      v_prime = Scope.new( v )
      args = []
      prog.functions[ e.name ].params.each_with_index do |param, i|
        v_prime[ param ] = eval( e.args[i], prog, v )
        args << v_prime[ param ]
      end
      pe_method = have_pe_method?( prog, e.name, args )
      if pe_method
        puts "-- pe_method = #{pe_method}"
        eval_pe_method( pe_method, args, prog, v )
      else
        eval( prog.functions[ e.name ].body, prog, v_prime )
      end
    when Call
      target = eval( e.target, prog, v )
      v_prime = Scope.new( v )
      v_prime[ :self ] = target
      if target.is_a? Class
        # class method
        if e.name == :new
          # instantiate an object
          puts "instantiating new #{target.name}" if DEBUG
          i = Instance.new( target )
          v_prime[ :self ] = i
          eval( Call.new( i, :initialize, e.args ), prog, v_prime )
          i
        else
          # handle ordinary class method
          eval( target.class_methods[ e.name ].body, prog, v_prime )
        end
      else
        # handle instance method, but first eval args
        v_prime[ :self ] = target
        instance_method = target.class_var.instance_methods[ e.name ]
        instance_method.params.each_with_index do |p, i|
          v_prime[p] = eval( e.args[i], prog, v )
        end
        eval( instance_method.body, prog, v_prime )
      end
    when Block
      e.exprs.each { |expr| eval( expr, prog, v ) }
    when Method
    when Class
      prog.constants[ e.name ] = e
    when Instance
      e
    else
      #raise StandardError.new( e.class )
    end
    
  end
  
  
  def self.peval( e, prog, v = Scope.new )
    puts "Partially Evaluating #{e.class}" if DEBUG
    
    case e
    when Literal
      e
    when Variable
      v[ e.name ] ? v[ e.name ] : e
    when Conditional
      pe_cond = peval( e.cond, prog, v )
      pe_e1 = peval( e.e1, prog, v )
      pe_e2 = peval( e.e2, prog, v )
      if pe.is_a? Literal
        (pe.value == true) ? pe_e1 : pe_e2
      else
        Conditional.new( pe, pe_e1, pe_e2 )
      end
    when Assignment
      pe = peval( e.expr, prog, v )
      if pe.is_a? Literal
        v[ e.identifier ] = pe
      else
        Assignment.new( e.identifier, pe )
      end
    when Call
      v_prime = Scope.new( v )
      prog.functions[ e.name ].params.each_with_index do |param, i|
        v_prime[ param ] = peval( e.args[i], prog, v )
      end
      peval( prog.functions[ e.name ].body, prog, v_prime )
    when Block
      e.exprs.map { |expr| peval( expr, prog, v ) }
    when Method
      # implement p-eval of a method
    else
      #raise StandardError.new( e.class )
    end
  end
  
  def self.mix( prog, method, args )
    puts "Mixing #{method} with args #{args.inspect}" if DEBUG
    
    function = prog.functions[ method ]
    puts prog.inspect
    
    raise StandardError.new("Incorrect number of arguments") if args.size != function.params.size
    
    v = Scope.new
    blank_params = []
    function.params.each_with_index do |param_name, i|
      if args[i] == :_
        blank_params << param_name
      else
        v[ param_name ] = Literal.new( args[i] )
      end
    end
    
    pe_body = peval( function.body, prog, v )
    
    pe_function_name = Program.pe_function_name( method, args )
    prog.pe_functions[pe_function_name] ||= {}
    prog.pe_functions[pe_function_name][args] = PEMethod.new( method, blank_params, args, pe_body )
  end
  
  def self.have_pe_method?( prog, method_name, args )
    pe_methods = prog.pe_functions[Program.pe_function_name(method_name, args)]
    return false unless pe_methods
    
    pe_methods.each do |k, v|
      next unless k.is_a? Array and k.length == args.length
      return v if args_match?( k, args )
    end
    false
  end
  
  def self.args_match?( pe_method_args, args )
    pe_method_args.each_with_index do |arg, i|
      return false if arg != :_ and arg != args[i]
    end
    true
  end
  
  def self.eval_pe_method( pe_method, args, prog, v )
    v_prime = Scope.new( v )
    param_index = 0
    pe_method.arguments.each_with_index do |param, i|
      if param == :_
        v_prime[ pe_method.params[param_index] ] = eval( args[i], prog, v )
      end
    end
    eval( pe_method.body, prog, v_prime )
  end
end
